package imoney.shared.model;

import java.util.ArrayList;

import javax.jdo.annotations.PersistenceCapable;
import javax.jdo.annotations.Persistent;

/**
 * It was chosen to store the debts separately from the 'group' class datas, in order to minimize datastore accesses.
 * It's of no use to query all the debts along other informations when just wanting to retrieve names of people inside
 * a group. The link between both entities is the ID, which should be the same (one debtManager instance created per group)
 * @author Arnaud PELOQUIN
 *
 */
// TODO : mettre group et debtManager dans la meme entite datastore ?

@PersistenceCapable
public class DebtManager extends Registreable {
	/**
	 * One debt per person of the corresponding group,
	 * except for the banker who has no debt object.
	 */
	@Persistent
	private ArrayList<Debt> debts;
	@Persistent
	private int banker;

	/**
	 * 
	 * @param ID
	 * @param list : must be populated with one new Debt for each people in the group.
	 * @param bankerID
	 * @throws Exception 
	 */
	public DebtManager(ArrayList<Debt> list, int bankerID) throws Exception{
		if(list.isEmpty()){
			throw new Exception("List of debts in debt manager constructor must be filled with new Debts");
		}
		debts = list;
		banker = bankerID;
	}
	
	/**
	 * Creates a DebtManager with the given ID and a list of debts corresponding to the list of persons.
	 * The banker is the first person of the list.
	 * @param ID : the ID of the debtManager to create. It is the ID of the related group.
	 * @param peopleNames : the pseudos of the people of the corresponding group.
	 */
	public DebtManager(int ID, ArrayList<Integer> peopleIDs) {
		this.banker = peopleIDs.get(0);
		// debts creation, one per person except for the banker which has none
		ArrayList<Debt> debtList = new ArrayList<Debt>();
		for (int i = 1; i<peopleIDs.size(); i++) {
			Debt debt = new Debt(peopleIDs.get(i));
			debtList.add(debt);
		}
		this.debts = debtList;
	}

	// GETTERS ---------------------------------------------
	public ArrayList<Debt> getDebts(){
		return debts;
	}
	public int getBanker(){
		return banker;
	}

	/**
	 * Beneficiaries and repartition must be ordered in exactly the same way.
	 * @param transac
	 */
	public void add(MoneyTransaction transac){
		// TODO : create a group-specific list of registered transactions to avoid mutli-check at 'Debt' level.
		// Curently a transaction can be added multiple times.
		update(transac.getPayer(),transac.getBeneficiaries(),transac.getAmount(),
				transac.getRepartition(), transac.getId());
	}
	/**
	 * Do nothing if the transaction isn't registered for this group.
	 * Will reverse the transaction effects and remove it's ID from the list.
	 * @param transac
	 */
	public void delete(MoneyTransaction transac){
		// Verification before deleting
		if(!getPersonalDebt(transac.getPayer()).hasRegisteredTransaction(transac.getId())){
			System.out.println("Transaction "+transac.getId()+ "wasn't registered for payer "+transac.getPayer()
					+", so nothing was deleted");
			return;
		}

		// Creation of opposed repartition to anihilate transac previous effect
		ArrayList<Double> opposedRepart = new ArrayList<Double>();
		for(double partialAmount : transac.getRepartition()){
			opposedRepart.add(-partialAmount);
		}
		update(transac.getPayer(),transac.getBeneficiaries(),-transac.getAmount(),opposedRepart, -transac.getId());
	}

	/**
	 * Generic method to manage transactions. 5 parameters instead of one transaction in parameter because of the
	 * impossibility to instanciate negative repartition transactions (a stability precaution)
	 * @param payer
	 * @param beneficiaries
	 * @param amount
	 * @param repartition
	 * @param transacID
	 */
	private void update(int payer, ArrayList<Integer> beneficiaries, double amount, ArrayList<Double> repartition,
			int transacID){

		/* here the payer is credited for the total amount of the payment. The next loop will take care of registering
		 a debt too, if he is also a beneficiary of the payment */
		if(payer!=banker){
			getPersonalDebt(payer).addAmount(amount, transacID);
		}

		int index = 0;
		for(int beneficiary : beneficiaries){

			/* The banker has no personal debt storage, so a payment to him is only represented as a credit to the payer,
			which was registered above. That's why this method do nothing is the beneficiary is the banker.
			The banker balance is calculated by summing all other personal debts in the group and switching the sign
			*/
			if(beneficiary!=banker){
				getPersonalDebt(beneficiary).addAmount(-repartition.get(index), transacID);
				index++;
			}
		}


	}
	/**
	 * Return the debt associated to the client ID for this debtmanager. The client might have other
	 * debts in other groups, they won't be returned. If the client is not present in the group, null is returned.
	 * @param ID
	 * @return
	 */
	public Debt getPersonalDebt(int ID){

		for(Debt dbt : debts){
			// TODO : optimiser avec ordre dans la liste, ou autre ?
			// No double debt check here, returns the first occurence.
			if(dbt.getClient()==ID){
				return dbt;
			}
		}
		return null;
	}
	
	
	
	// MAIN ------------------------------------------------------------------- 

	public static void main(String[] args){
		// TODO : run some more tests for core functionalities add + delete

		// CREATION DU MANAGER
		ArrayList<Debt> dettes = new ArrayList<Debt>();
		for(int i=0; i<4; i++){
			Debt dbt = new Debt(i);
			dettes.add(dbt);
		}
		try {
			DebtManager manager = new DebtManager(dettes,3);

			// TRANSACTIONs CREATION      (montant, intitul, payer, beneficiaires)
			ArrayList<Integer> benefs = new ArrayList<Integer>();
			benefs.add(0);
			benefs.add(1);
			benefs.add(2);
			benefs.add(3);
			
			ArrayList<Double> repart = new ArrayList<Double>();
			repart.add((double)5);
			repart.add((double)15);
			repart.add((double)1);
			repart.add((double)19);

			try {
				MoneyTransaction transac = new MoneyTransaction(40,"test",0,benefs, repart);


				manager.add(transac);
				String bankInfo="";
				for(int i=0;i<4; i++){
					if(i==manager.getBanker()){bankInfo=" (banker)";}
					System.out.println(manager.getDebts().get(i).toString()+bankInfo+"\n");
					bankInfo="";
				}
				
			} catch (Exception e) {
				e.printStackTrace();
			}

		} catch (Exception e) {
			System.out.println(e.getMessage());
		}
	}

	@Override
	public String getAvailabilityQuery() {
		String query = "select from "+DebtManager.class.getName()+" where id=="+getId();
		return query;
	}

	@Override
	public String getAvailabilityExceptionMessage() {
		return "All DebtManager availability query should return silently, this exception shouldn't have occured.";
	}
}
